JPA-EntityManager, PersistenceContext

(부제: 그리고 Transaction)

#Spring #Spring_JDBC #Spring_JPA #Spring_DB

이전 포스팅:
JDBC와 JPA

연관 포스팅:
Transaction - JPA, JDBC, DB


1. EntityManager

앞선 포스트에서 JPA는 ORM을 위한 표준 인터페이스 벌크라고 설명했다. ORM(Object Relation Mapping)은 데이터베이스의 데이터를 객체로 취급해서 다룰 수 있게 하는 기능이다. 객체 그러니까 Entity로 다룰 수 있게 해주는 기술이 JPA이기 때문에 EntityManager는 JPA의 핵심이라고 할 수 있다.


2. EntityManager has a PersistenceContext

여기서 한번쯤 눈여겨 볼 것은 EntityManager의 "Manager" 라는 것인데 Entity"들"을 관리하는 의미이다. 이 엔티티"들"을 저장하는 자료구조가 PersistenceContext 인 것이다. 자료구조라고 표현한 이유는 Oracle JPA Specification에서 언급한 것 처럼 말 그대로 엔티티들의 집합이기 때문이다. (Spring Boot JPA 1차 캐시 정리, dingdingmin 2, 3 참고 )

A persistence context is a set of managed entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed by the entity manager.

statefulpersistencecontext.png
(+Android나 스프링이나 모두 ApplicationContext등의 '컨텍스트'라는 객체를 가지고있다. 이 개념을 받아들이는게 어려웠던 이유는 컨텍스트는 한국어로 '맥락'이라고 하고 맥락이라 함은 말이나 생각을 할 때 고려해야하는 상황정도의 추상적인 의미로 다가오기 때문이다. 지금에 와서 생각하면 이보다 더 적절한 네이밍이 없긴 한데, 처음 받아들인다면 '필요한 것들을 모두다 때려넣은 정보 집합' 정도로 받아들여보기 시작하자.)


3. Entity, EntityLifecycle within a Transaction

EntityManager는 Entity들을 관리한다. Entity가 '관리'될 수 있는 대상이라는 의미는 관리해야할 상태나 값이 있다는 뜻이다. Entity의 생성부터 소멸의 상태 변화들을 Entity Lifecycle이라고 한다.(영속성 컨텍스트(Persistence Context), SeongWon Oh 참고) 그럼 Entity의 생성과 소멸의 범위를 결정짓는건 뭘까? 애플리케이션이 구동되면서부터 모든 Entity들이 살아나고 애플리케이션이 종료되야 Entity들이 소멸될까?

이 문장이 참 중요한데, Entity의 생존 최대 범위는 '트랜잭션 동안' 이다. 이 엔티티들을 관리하는 EntityManager도 트랜잭션의 영향 아래 있는 것이다. 그래서 트랜잭션이 시작되면 EntityManager가 생성되고 EntityManager가 생성되니까 Entity들을 저장할 그릇인 PersistenceContext가 그 안에 있게 되고 그래서 트랜잭션과 영속성 컨텍스트를 묶어서 생각하게 되는 것이다.

이 부분에서 어떤 헷갈림이 오랫동안 풀리지 않았는데, 이것을 이 글을 보고있는 분들과 같이 생각해보고 싶다.


3-1. EntityManager와 PersistenceContext의 관계는 1:N 혹은 N:1이 될 수 있을까?

이 질문에 대한 답변이 모든 블로그 글마다 다르게 느껴졌다. 결론적으로는 어떤 제한적인 문맥에서는 맞을수도 있고 틀릴 수도 있다. 고 말할 수 있겠다고 생각한다. (이 부분에 대해서 잘 아시는 분이 있어서 들을 수 있으면 얼마나 좋을까.)

3-1-1. 내가 코드를 보기로는 1:1 인데???

처음엔 EntityManager와 PersistenceContext의 관계는 1:1 이라고 생각했다. 그래야만 했다. EntityManager(SessionImpl)은 Transaction이 시작할 때 생성하고 PersistenceContext(StatefulPersistenceContext)는 EntityManager(SessionImpl)이 가지고 있는 객체로 이것도 EntityManager(SessionImpl) 생성자에서 생성되니까.
sessionImpl_createPersistenceContext.png

3-1-2. 근거 없는 낭설은 아니겠지!

그런데 1:N 혹은 N:1을 설명하는 블로그가 있다는 것은 분명 이유가 있을 것 같았다. 그래서 그 내용의 근원을 찾으려고 노력했는데, Oracle JPA Specification에 비슷한 내용이 있는 것을 보게되었다.

Oracle JPA 2.0 Specification

In Java EE environments, a JTA transaction typically involves calls across multiple components. Such components may often need to access the same persistence context within a single transaction. To facilitate such use of entity managers in Java EE environments, when an entity manager is injected into a component or looked up directly in the JNDI naming context, its persistence context will automatically be propagated with the current JTA transaction, and the EntityManager references that are mapped to the same persistence unit will provide access to this same persistence context within the JTA transaction. This propagation of persistence context by the Java EE container avoids the need for the application to pass references to EntityManager instances from one component to another. An entity manager for which the container manages the persistence context in this manner is termed a container-managed entity manager. A container-managed entity manager’s lifecycle is managed by the Java EE container.

이것을 한국어로 번역하면,

Java EE 환경에서, 하나의 JTA 트랜잭션은 일반적으로 다수의 컴포넌트에서 발생하는 호출과 연관됩니다. 그런 컴포넌트들은 단일 트랜잭션에서 같은 영속성 컨텍스트에 접근해야 할것입니다. Java EE환경에서 entity manager 사용을 효율적으로 하기위해, entity manager 컴포넌트에 주입하거나 JNDI naming context에서 직접 찾을 때 그 영속성 컨텍스트는 자동적으로 현재의 JTA 트랜잭션과 함께 전파되고, 같은 영속성 컨텍스트 단위에 매핑된 EntityManager 참조(*[개인적인 생각] EntityManager 내에 있는 persistence context 객체를 의미한다고 생각.)는 JTA 트랜잭션 내의 같은 영속성 컨텍스트에 대한 접근을 제공할 것입니다. Java EE 컨테이너에 의한 이 영속성 컨텍스트의 전파는 애플리케이션이 EntityManager 인스턴스에 대한 참조를 한 컴포넌트에서 다른 컴포넌트로 넘기는 것을 방지합니다. 이러한 방식으로 영속성 객체를 관리하는 컨테이너의 entity manager를 container-managed entity manager라고 명명합니다. container-managed entity manager의 라이프사이클은 Java EE container에 의해서 관리됩니다.

이다.
EntityManager와 PersistenceContext가 N:1의 관계가 맞다는 글로 읽혀진다. 그럼 위의 SessionImpl은 뭐지? JPA 구현체이니만큼 얘도 이 글의 작동방식과 같아야 하는데? 하는 생각이 든다. 지피티한테도 물어봤다.

gpt_entitymanager_possible1.png

아니란다. 그래서 이 글을 보여주고 다시 물어봤다.
gpt_entitymanager_possible2.png

(ㅋㅋㅋㅋ AI 한테서 고집이 느껴질 일인가...)
지피티의 말인 즉슨,
'EntityManager와 PersistenceContext는 1:1 관계가 백퍼만퍼 맞는건데, CME(Container-Managed Entity Manager)라는건 좀 특별해서 일반적인 EntityManager가 아니라 컨테이너 환경에서 관리해주는 애야. 이걸 일반적이라고 생각하지 마. 얘만 독특하게 그렇게 하더라고. 그리고 너가 헷갈리는게 더 있는거 같은데 CME가 있으면 AME(Application-Managed Entity Manager)도 있다? AME를 쓰면 내 말대로 동작할걸?'
이였다.

ㅎㅎ 지피티의 말을 보고 다시 Oracle spec. 문장을 읽으니 분명 끝마무리 즈음에 container-managed라는 것이 있었다. 이것과 함께 다시 여러 블로그들을 보다가 힌트가 될만한 부분을 찾아내었다. Spring Container는 JPA EntityManager의 Thread-Safety를 어떻게 보장할까?, Yoo Young-mo님의 포스팅을 보면 Spring에서는 AOP 프록시를 통해 EntityManager를 생성하는데, 여기서 SharedEntityManagerCreator의 주석설명 부분에 synchronizedWithTransaction 파라미터의 설명을 보면 '진행중인 트랜잭션에 참여하는지의 여부'라는 설명이 있다. persistencecontext는 트랜잭션 단위로 존재하기 때문에, 지금까지의 상황을 종합해보면 한 트랜잭션 안에 여러개의 EntityManager가 존재할 수 있고, 트랜잭션 안에 존재하는 EntityManager에게는persistencecontext가 존재할 수 있다.는 이해가 되었다. 컨테이너가 프록시를 통해 EntityManager를 관리한다는 것이 CME의 설명과 일맥상통한다.

그래서 나의 결론은 원리적인 상황에서는 EntityManager와 PersistenceContext는 one to one의 관계, 스프링이 제공해주는 컨테이너 환경에서는 many to one의 관계일 수 있다. 이다.

3-1-3. 그럼 1:N은..?

이 부분에 대한 근거는 찾지 못했다. 직접 테스트를 해봐도 트랜잭션에 따라서 EntityManager가 생성되기는 해도 있던 EntityManager에 PersistenceContext가 교체되서 사용되는 일은 없었다. 1:N은 불필요하다고 생각한다.